package lz.controls
{

	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.text.TextFormat;
	import flash.ui.Keyboard;
	
	import lz.controls.BaseButton;
	import lz.controls.List;
	import lz.controls.TextInput;
	import lz.controls.core.InvalidationType;
	import lz.controls.core.UIComponent;
	import lz.controls.data.DataProvider;
	import lz.controls.data.SimpleCollectionItem;
	import lz.controls.events.ComponentEvent;
	import lz.controls.events.DataChangeEvent;
	import lz.controls.events.ListEvent;
	import lz.controls.listClasses.ICellRenderer;
	import lz.controls.managers.IFocusManagerComponent;

	//--------------------------------------
	//  Class description
	//--------------------------------------
	/**
	 * ComboBox 组件包含一个下拉列表，用户可以从该列表中选择单个值。 
	 * 其功能与 HTML 中的 SELECT 表单元素的功能相似。
	 * ComboBox 组件可以是可编辑的，在这种情况下，用户可以在 ComboBox 组件的 TextInput 部分键入不在列表中的条目。
	 */
	public class ComboBox extends UIComponent implements IFocusManagerComponent
	{
		/**
		 * @private (protected)
		 */
		protected var inputField:TextInput;
		/**
		 * @private (protected)
		 */
		protected var background:BaseButton;
		/**
		 * @private (protected)
		 */
		protected var list:List;
		/**
		 * @private (protected)
		 */
		protected var _rowCount:uint=5;
		/**
		 * @private (protected)
		 */
		protected var _editable:Boolean=false;
		/**
		 * @private (protected)
		 */
		protected var isOpen:Boolean=false;
		/**
		 * @private (protected)
		 */
		protected var highlightedCell:int=-1
		/**
		 * @private (protected)
		 */
		protected var editableValue:String;
		/**
		 * @private (protected)
		 */
		protected var _prompt:String;

		/**
		 * @private (protected)
		 */
		protected var isKeyDown:Boolean=false;
		/**
		 * @private (protected)
		 */
		protected var currentIndex:int;

		/**
		 * @private (protected)
		 */
		protected var listOverIndex:uint;

		/**
		 * @private (protected)
		 */
		protected var _dropdownWidth:Number;

		/**
		 * @private (protected)
		 */
		protected var _labels:Array;

		/**
		 * @private
		 */
		private var collectionItemImport:SimpleCollectionItem;

		/**
		 * @private
		 */
		private static var defaultStyles:Object={upSkin: "ComboBox_upSkin", downSkin: "ComboBox_downSkin", overSkin: "ComboBox_overSkin", disabledSkin: "ComboBox_disabledSkin", focusRectSkin: null, focusRectPadding: null, textFormat: null, disabledTextFormat: null, textPadding: 3, buttonWidth: 24, disabledAlpha: null, listSkin: null};

		/**
		 * @private (protected)
		 */
		protected static const LIST_STYLES:Object={upSkin: "comboListUpSkin", overSkin: "comboListOverSkin", downSkin: "comobListDownSkin", disabledSkin: "comboListDisabledSkin", downArrowDisabledSkin: "downArrowDisabledSkin", downArrowDownSkin: "downArrowDownSkin", downArrowOverSkin: "downArrowOverSkin", downArrowUpSkin: "downArrowUpSkin", upArrowDisabledSkin: "upArrowDisabledSkin", upArrowDownSkin: "upArrowDownSkin", upArrowOverSkin: "upArrowOverSkin", upArrowUpSkin: "upArrowUpSkin", thumbDisabledSkin: "thumbDisabledSkin", thumbDownSkin: "thumbDownSkin", thumbOverSkin: "thumbOverSkin", thumbUpSkin: "thumbUpSkin", thumbIcon: "thumbIcon", trackDisabledSkin: "trackDisabledSkin", trackDownSkin: "trackDownSkin", trackOverSkin: "trackOverSkin", trackUpSkin: "trackUpSkin", repeatDelay: "repeatDelay", repeatInterval: "repeatInterval", textFormat: "textFormat", disabledAlpha: "disabledAlpha", skin: "listSkin"};
		/**
		 * @private (protected)
		 */
		protected static const BACKGROUND_STYLES:Object={overSkin: "overSkin", downSkin: "downSkin", upSkin: "upSkin", disabledSkin: "disabledSkin", repeatInterval: "repeatInterval"};
		/**
		 * 
		 */
		public static function getStyleDefinition():Object
		{
			return mergeStyles(defaultStyles, List.getStyleDefinition());
		}
		/**
		 * @private (internal)
		 */
		public static var createAccessibilityImplementation:Function;

		/**
		 * Creates a new ComboBox component instance.
		 */
		public function ComboBox()
		{
			super();
		}
		/**
		 * 获取或设置一个布尔值，该值指示 ComboBox 组件为可编辑还是只读。
		 *  true 值指示 ComboBox 组件可编辑；false 值指示该组件不可编辑。
		 * 在可编辑 ComboBox 组件中，用户可以在文本框中输入下拉列表中未显示的值。 
		 * 文本框显示列表中的项目的文本。 如果 ComboBox 不可编辑，则不能在文本框中输入文本。
		 * 默认值为 false。
		 */
		public function get editable():Boolean
		{
			return _editable;
		}
		public function set editable(value:Boolean):void
		{
			_editable=value;
			drawTextField();
		}
		/**
		 * 获取或设置没有滚动条的下拉列表中可显示的最大行数。 
		 * 如果下拉列表中的项数超过该值，则会调整列表的大小，并在必要时显示滚动条。 
		 * 如果下拉列表中的项数小于该值，则会调整下拉列表的大小以适应其所包含的项数。
		 * 此行为与 List 组件的行为不同，List 组件会始终显示由其 rowCount 属性指定的行数，即使其中出现空白也是如此。
		 * 默认值为 5。
		 */
		public function get rowCount():uint
		{
			return _rowCount;
		}
		public function set rowCount(value:uint):void
		{
			_rowCount=value;
			invalidate(InvalidationType.SIZE);
		}
		/**
		 * 获取或设置用户可以在文本字段中输入的字符。 
		 * 如果 restrict 属性的值为一串字符，则只能在文本字段中输入该字符串中的字符。
		 * 字符串的读取顺序为从左到右。如果 restrict 属性的值为 null，则可以输入任何字符。
		 * 如果 restrict 属性的值为空字符串 ("")，则不能输入任何字符。
		 * 可以使用连字符 (-) 指定一个范围。它只限制用户交互；脚本可将任何字符放入文本字段中。
		 * 默认值为 null。
		 */
		public function get restrict():String
		{
			return inputField.restrict;
		}
		public function set restrict(value:String):void
		{
			if (componentInspectorSetting && value == "")
			{
				value=null;
			}
			if (!_editable)
			{
				return;
			}
			inputField.restrict=value;
		}
		/**
		 *获取或设置单选列表中的选定项目的索引。 
		 * 单选列表是一次只能从中选择一个项目的列表。
		 * 值为 -1 指示未选择任何项目；如果选择了多项，则该值等于所选的一组项目中最后一个选定项目的索引。
		 * 使用 ActionScript 设置此属性时，位于指定索引处的项目将替换当前选定的内容。 
		 * 通过编程方式更改选定内容时，不会调度 change 事件对象。
		 * 默认值为 0。
		 */
		public function get selectedIndex():int
		{
			return list.selectedIndex;
		}
		public function set selectedIndex(value:int):void
		{
			list.selectedIndex=value;
			highlightCell(); // Deselect any highlighted cells / reset index
			invalidate(InvalidationType.SELECTED);
		}
		/**
		 * 获取或设置可编辑 ComboBox 组件中文本框所包含的文本。 
		 * 对于不可编辑的 ComboBox 组件，该值为只读。
		 * 默认值为 ""。
		 */
		public function get text():String
		{
			return inputField.text;
		}
		public function set text(value:String):void
		{
			if (!editable)
			{
				return;
			}
			inputField.text=value;
		}
		/**
		 * 获取或设置 dataProvider 对象中的字段名称，该字段名称将显示为 TextInput 字段和下拉列表的标签。
		 * 默认情况下，组件将显示各 dataProvider 项目的 label 属性。 
		 * 如果 dataProvider 项目不包含 label 属性，则可以设置 labelField 属性以使用其他属性。
		 * 注意：labelField 属性不在 labelFunction 属性设置为回调函数时使用。
		 * 默认值为 "label"。
		 */
		public function get labelField():String
		{
			return list.labelField;
		}
		public function set labelField(value:String):void
		{
			list.labelField=value;
			invalidate(InvalidationType.DATA);
		}
		/**
		 * 获取或设置用于获得项的标签的函数。
		 * 默认情况下，组件将显示 dataProvider 项目的 label 属性。 
		 * 但某些数据集可能没有 label 字段，或者没有不需修改其值即可用作标签的字段。 
		 * 例如，某给定数据集可能存储了全名，但将全名分开保存在 lastName 和 firstName 字段中。 在这种情况下，此属性可能用来设置一个回调函数，该函数将 lastName 和 firstName 字段的值连接在一起成为全名字符串以供显示。
		 * 注意：labelField 属性不在 labelFunction 属性设置为回调函数时使用。
		 */
		public function get labelFunction():Function
		{
			return list.labelFunction;
		}
		public function set labelFunction(value:Function):void
		{
			list.labelFunction=value;
			invalidate(InvalidationType.DATA);
		}
		/**
		 * 基于 labelField 和 labelFunction 属性检索渲染器将为给定数据对象显示的字符串。
		 * 注意：labelField 不在 labelFunction 属性设置为回调函数时使用。
		 * 参数
		 * item:Object — 要呈现的对象。
		 * 返回
		 * String — 根据数据要显示的字符串。
		 */
		public function itemToLabel(item:Object):String
		{
			if (item == null)
			{
				return "";
			}
			return list.itemToLabel(item);
		}
		/**
		 * 获取或设置下拉列表中所选项目的值。
		 * 如果用户在可编辑 ComboBox 组件的文本框中输入文本，则 selectedItem 属性为 undefined。
		 * 只有当用户手动或使用 ActionScript 从下拉框中选择一个项目时，此属性才具有值。如果 ComboBox 组件不可编辑，则 selectedItem 属性值始终有效。
		 * 如果可编辑 ComboBox 组件的下拉列表中不存在任何项目，则此属性值为 null。
		 * 默认值为 null。
		 */
		public function get selectedItem():Object
		{
			return list.selectedItem;
		}
		public function set selectedItem(value:Object):void
		{
			list.selectedItem=value;
			invalidate(InvalidationType.SELECTED);
		}
		/**
		 *获取对 ComboBox 组件所包含的 List 组件的引用。 直到必须显示 List 子组件时才会在 ComboBox 内将其实例化。 但是，访问 dropdown 属性时会创建列表。
		 */
		public function get dropdown():List
		{
			return list;
		}
		/**
		 *  获取列表中的项目数。
		 */
		public function get length():int
		{
			return list.length;
		}
		/**
		 * 获取对 ComboBox 组件所包含的 TextInput 组件的引用。
		 */
		public function get textField():TextInput
		{
			return inputField;
		}
		/**
		 *  获取可编辑 ComboBox 组件中的项目的标签。
		 */
		public function get value():String
		{
			if (editableValue != null)
			{
				return editableValue;
			}
			else
			{
				var item:Object=selectedItem;
				if (!_editable && item.data != null)
				{
					return item.data;
				}
				else
				{
					return itemToLabel(item);
				}
			}
		}
		/**
		 * 获取或设置要查看的项目列表的数据模型。 
		 * 数据提供程序可由多个基于列表的组件共享。 
		 * 对数据提供程序所做的更改会立即应用于将其用作数据源的所有组件。
		 */
		public function get dataProvider():DataProvider
		{
			return list.dataProvider;
		}
		public function set dataProvider(value:DataProvider):void
		{
			value.addEventListener(DataChangeEvent.DATA_CHANGE, handleDataChange, false, 0, true);
			list.dataProvider=value;
			invalidate(InvalidationType.DATA);
		}
		/**
		 * 获取或设置下拉列表的最大宽度，以像素为单位。 
		 * 此属性的默认值为 ComboBox 组件的宽度（TextInput 实例的宽度加上 BaseButton 实例的宽度）。
		 * 默认值为 100。
		 */
		public function get dropdownWidth():Number
		{
			return list.width;
		}
		public function set dropdownWidth(value:Number):void
		{
			_dropdownWidth=value;
			invalidate(InvalidationType.SIZE);
		}
		/**
		 */
		public function addItem(item:Object):void
		{
			list.addItem(item);
			invalidate(InvalidationType.DATA);
		}
		/**
		 * 获取或设置对 ComboBox 组件的提示。 
		 * 此提示是一个字符串，当 selectedIndex 为 -1 时，会显示在 ComboBox 的 TextInput 部分中。 
		 * 它通常是一个类似于“Select one...”的字符串。 
		 * 如果未设置提示，则 ComboBox 组件将 selectedIndex 属性设置为 0 并在 dataProvider 属性中显示第一个项目。
		 * 默认值为 ""。
		 */
		public function get prompt():String
		{
			return _prompt;
		}
		public function set prompt(value:String):void
		{
			if (value == "")
			{
				_prompt=null;
			}
			else
			{
				_prompt=value;
			}
			invalidate(InvalidationType.STATE);
		}

		/**
		 * @private (protected)
		 */
		public function get imeMode():String
		{
			return inputField.imeMode;
		}
		public function set imeMode(value:String):void
		{
			inputField.imeMode=value;
		}
		/**
		 */
		public function addItemAt(item:Object, index:uint):void
		{
			list.addItemAt(item, index);
			invalidate(InvalidationType.DATA);
		}
		/**
		 */
		public function removeAll():void
		{
			list.removeAll();
			inputField.text="";
			invalidate(InvalidationType.DATA);
		}
		/**
		 */
		public function removeItem(item:Object):Object
		{
			return list.removeItem(item);
		}
		/**
		 * 删除指定索引位置处的项目
		 *
		 */
		public function removeItemAt(index:uint):void
		{
			list.removeItemAt(index);
			invalidate(InvalidationType.DATA);
		}
		/**
		 */
		public function getItemAt(index:uint):Object
		{
			return list.getItemAt(index);
		}
		/**
		 */
		public function replaceItemAt(item:Object, index:uint):Object
		{
			return list.replaceItemAt(item, index);
		}
		/**
		 */
		public function sortItems(... sortArgs:Array):*
		{
			return list.sortItems.apply(list, sortArgs);
		}
		/**
		 */
		public function sortItemsOn(field:String, options:Object=null):*
		{
			return list.sortItemsOn(field, options);
		}
		/**
		 * Opens the drop-down list.
		 *
		 * <p><strong>Note:</strong> Calling this method causes the <code>open</code>
		 * event to be dispatched. If the ComboBox component is already open, calling this method has no effect.</p>
		 */
		public function open():void
		{
			currentIndex=selectedIndex;
			if (isOpen || length == 0)
			{
				return;
			}

			dispatchEvent(new Event(Event.OPEN));
			isOpen=true;

			// Add a listener to the stage to close the combobox when something
			// else is clicked.  We need to wait a frame, otherwise the same click
			// that opens the comboBox will also close it.
			addEventListener(Event.ENTER_FRAME, addCloseListener, false, 0, true);

			positionList();
			list.scrollToSelected();
			stage.addChild(list);
		}
		/**
		 * Closes the drop-down list.
		 *
		 * <p><strong>Note:</strong> Calling this method causes the <code>close</code>
		 * event to be dispatched. If the ComboBox is already closed, calling this method has no effect.</p>
		 */
		public function close():void
		{
			highlightCell();
			highlightedCell=-1;
			if (!isOpen)
			{
				return;
			}

			dispatchEvent(new Event(Event.CLOSE));
			stage.removeEventListener(MouseEvent.MOUSE_DOWN, onStageClick);
			isOpen=false;
			stage.removeChild(list);
		}

		/**
		 * Gets the string that is displayed in the TextInput portion
		 * of the ComboBox component. This value is calculated from the data by using
		 * the <code>labelField</code> or <code>labelFunction</code> property.
		 */
		public function get selectedLabel():String
		{
			if (editableValue != null)
			{
				return editableValue;
			}
			else if (selectedIndex == -1)
			{
				return null;
			}
			return itemToLabel(selectedItem);
		}
		/**
		 * @private (protected)
		 */
		override protected function configUI():void
		{
			super.configUI();

			background=new BaseButton();
			background.focusEnabled=false;
			copyStylesToChild(background, BACKGROUND_STYLES);
			background.addEventListener(MouseEvent.MOUSE_DOWN, onToggleListVisibility, false, 0, true);
			addChild(background);

			inputField=new TextInput();
			inputField.focusTarget=this as IFocusManagerComponent;
			inputField.focusEnabled=false;
			inputField.addEventListener(Event.CHANGE, onTextInput, false, 0, true);
			addChild(inputField);

			list=new List();
			list.focusEnabled=false;
			copyStylesToChild(list, LIST_STYLES);
			list.addEventListener(Event.CHANGE, onListChange, false, 0, true);
			list.addEventListener(ListEvent.ITEM_CLICK, onListChange, false, 0, true);
			list.addEventListener(ListEvent.ITEM_ROLL_OUT, passEvent, false, 0, true);
			list.addEventListener(ListEvent.ITEM_ROLL_OVER, passEvent, false, 0, true);
			list.verticalScrollBar.addEventListener(Event.SCROLL, passEvent, false, 0, true);

		}
		/**
		 * @private (protected)
		 */
		override protected function focusInHandler(event:FocusEvent):void
		{
			super.focusInHandler(event);
			if (editable)
			{
				stage.focus=inputField.textField;
			}
		}
		/**
		 * @private (protected)
		 */
		override protected function focusOutHandler(event:FocusEvent):void
		{
			isKeyDown=false;
			// If the dropdown is open...
			if (isOpen)
			{
				// If focus is moving outside the dropdown...
				if (!event.relatedObject || !list.contains(event.relatedObject))
				{
					// Close the dropdown.
					if (highlightedCell != -1 && highlightedCell != selectedIndex)
					{
						selectedIndex=highlightedCell;
						dispatchEvent(new Event(Event.CHANGE));
					}
					close();
				}
			}
			super.focusOutHandler(event);
		}
		/**
		 * @private (protected)
		 */
		protected function handleDataChange(event:DataChangeEvent):void
		{
			invalidate(InvalidationType.DATA);
		}
		/**
		 * @private (protected)
		 */
		override protected function draw():void
		{
			// Fix the selectedIndex before redraw.
			var _selectedIndex :int=selectedIndex;

			// Check if index is -1, and it is allowed.
			if (_selectedIndex == -1 && (prompt != null || editable || length == 0))
			{
				_selectedIndex=Math.max(-1, Math.min(_selectedIndex, length - 1));
			}
			else
			{
				editableValue=null;
				_selectedIndex=Math.max(0, Math.min(_selectedIndex, length - 1));
			}
			if (list.selectedIndex != _selectedIndex)
			{
				list.selectedIndex=_selectedIndex;
				invalidate(InvalidationType.SELECTED, false);
			}

			if (isInvalid(InvalidationType.STYLES))
			{
				setStyles();
				setEmbedFonts();
				invalidate(InvalidationType.SIZE, false);
			}
			if (isInvalid(InvalidationType.SIZE, InvalidationType.DATA, InvalidationType.STATE))
			{
				drawTextFormat();
				drawLayout();
				invalidate(InvalidationType.DATA);
			}
			if (isInvalid(InvalidationType.DATA))
			{
				drawList();
				invalidate(InvalidationType.SELECTED, true);
			}
			if (isInvalid(InvalidationType.SELECTED))
			{
				if (_selectedIndex == -1 && editableValue != null)
				{
					inputField.text=editableValue;
				}
				else if (_selectedIndex > -1)
				{
					if (length > 0)
					{
						inputField.horizontalScrollPosition=0;
						inputField.text=itemToLabel(list.selectedItem);
					}
				}
				else if (_selectedIndex == -1 && _prompt != null)
				{
					showPrompt();
				}
				else
				{
					inputField.text="";
				}

				if (editable && selectedIndex > -1 && stage.focus == inputField.textField)
				{
					inputField.setSelection(0, inputField.length);
				}
			}
			drawTextField();


			super.draw();
		}
		/**
		 * @private (protected)
		 */
		protected function setEmbedFonts():void
		{
			var embed:Object=getStyleValue("embedFonts");
			if (embed != null)
			{
				inputField.textField.embedFonts=embed;
			}
		}
		/**
		 * @private (protected)
		 */
		protected function showPrompt():void
		{
			inputField.text=_prompt;
		}
		/**
		 * @private (protected)
		 */
		protected function setStyles():void
		{
			copyStylesToChild(background, BACKGROUND_STYLES);
			copyStylesToChild(list, LIST_STYLES);
		}

		/**
		 * @private (protected)
		 */
		protected function drawLayout():void
		{
			var buttonWidth:Number=getStyleValue("buttonWidth") as Number;
			var textPadding:Number=getStyleValue("textPadding") as Number;
			background.setSize(width, height);
			inputField.x=inputField.y=textPadding;
			inputField.setSize(width - buttonWidth - textPadding, height - textPadding); // textPadding*2 cuts off the descenders.

			list.width=(isNaN(_dropdownWidth)) ? width : _dropdownWidth;

			background.enabled=enabled;
			background.drawNow();
		}

		/**
		 * @private (protected)
		 */
		protected function drawTextFormat():void
		{
			var tf:TextFormat=getStyleValue(_enabled ? "textFormat" : "disabledTextFormat") as TextFormat;
			if (tf == null)
			{
				tf=new TextFormat();
			}
			inputField.textField.defaultTextFormat=tf;
			inputField.textField.setTextFormat(tf);
			setEmbedFonts();
		}

		/**
		 * @private (protected)
		 */
		protected function drawList():void
		{
			list.rowCount=Math.max(0, Math.min(_rowCount, list.dataProvider.length));
		}

		/**
		 * @private (protected)
		 */
		protected function positionList():void
		{
			var p:Point=localToGlobal(new Point(0, 0));
			list.x=p.x;
			if (p.y + height + list.height > stage.stageHeight)
			{
				list.y=p.y - list.height;
			}
			else
			{
				list.y=p.y + height;
			}
		}

		/**
		 * @private (protected)
		 */
		protected function drawTextField():void
		{
			inputField.setStyle("upSkin", "");
			inputField.setStyle("disabledSkin", "");

			inputField.enabled=enabled;
			inputField.editable=_editable;
			inputField.textField.selectable=enabled && _editable;
			inputField.mouseEnabled=inputField.mouseChildren=enabled && _editable;
			inputField.focusEnabled=false;

			if (_editable)
			{
				inputField.addEventListener(FocusEvent.FOCUS_IN, onInputFieldFocus, false, 0, true);
				inputField.addEventListener(FocusEvent.FOCUS_OUT, onInputFieldFocusOut, false, 0, true);
			}
			else
			{
				inputField.removeEventListener(FocusEvent.FOCUS_IN, onInputFieldFocus);
				inputField.removeEventListener(FocusEvent.FOCUS_OUT, onInputFieldFocusOut);
			}

		}

		/**
		 * @private (protected)
		 */
		protected function onInputFieldFocus(event:FocusEvent):void
		{
			inputField.addEventListener(ComponentEvent.ENTER, onEnter, false, 0, true);
			close();
		}

		/**
		 * @private (protected)
		 */
		protected function onInputFieldFocusOut(event:FocusEvent):void
		{
			inputField.removeEventListener(ComponentEvent.ENTER, onEnter);
			selectedIndex=selectedIndex;
		}

		/**
		 * @private (protected)
		 */
		protected function onEnter(event:ComponentEvent):void
		{
			event.stopPropagation();
		}

		/**
		 * @private (protected)
		 */
		protected function onToggleListVisibility(event:MouseEvent):void
		{
			event.stopPropagation();
			dispatchEvent(event);
			if (isOpen)
			{
				close()
			}
			else
			{
				open();
				// Add a listener to listen for press/drag/release behavior.
				// We will remove it once they release.
				stage.addEventListener(MouseEvent.MOUSE_UP, onListItemUp, false, 0, true);
			}
		}


		/**
		 * @private (protected)
		 */
		protected function onListItemUp(event:MouseEvent):void
		{
			stage.removeEventListener(MouseEvent.MOUSE_UP, onListItemUp);
			if (!(event.target is ICellRenderer) || !list.contains(event.target as DisplayObject))
			{
				return;
			}

			editableValue=null;
			var startIndex:int=selectedIndex;
			selectedIndex=event.target.listData.index;

			if (startIndex != selectedIndex)
			{
				dispatchEvent(new Event(Event.CHANGE));
			}

			close();
		}

		/**
		 * @private (protected)
		 */
		protected function onListChange(event:Event):void
		{
			editableValue=null;
			dispatchEvent(event);
			invalidate(InvalidationType.SELECTED);
			if (isKeyDown)
			{
				return;
			}
			close();
		}

		/**
		 * @private (protected)
		 */
		protected function onStageClick(event:MouseEvent):void
		{
			if (!isOpen)
			{
				return;
			}
			if (!contains(event.target as DisplayObject) && !list.contains(event.target as DisplayObject))
			{
				if (highlightedCell != -1)
				{
					selectedIndex=highlightedCell;
					dispatchEvent(new Event(Event.CHANGE));
				}
				close();
			}
		}

		/**
		 * @private (protected)
		 */
		protected function passEvent(event:Event):void
		{
			dispatchEvent(event);
		}

		/**
		 * @private (protected)
		 */
		private function addCloseListener(event:Event):void
		{
			removeEventListener(Event.ENTER_FRAME, addCloseListener);
			if (!isOpen)
			{
				return;
			}
			stage.addEventListener(MouseEvent.MOUSE_DOWN, onStageClick, false, 0, true);
		}

		/**
		 * @private (protected)
		 */
		protected function onTextInput(event:Event):void
		{
			// Stop the TextInput CHANGE event
			event.stopPropagation();
			if (!_editable)
			{
				return;
			}
			// If editable, set the editableValue, and dispatch a change event.
			editableValue=inputField.text;
			selectedIndex=-1;
			dispatchEvent(new Event(Event.CHANGE));
		}

		/**
		 * @private (protected)
		 */
		protected function calculateAvailableHeight():Number
		{
			var pad:Number=Number(getStyleValue("contentPadding"));
			return list.height - pad * 2;
		}

		/**
		 * @private (protected)
		 */
		override protected function keyDownHandler(event:KeyboardEvent):void
		{
			isKeyDown=true;
			if (event.ctrlKey)
			{
				switch (event.keyCode)
				{
					case Keyboard.UP:
						if (highlightedCell > -1)
						{
							selectedIndex=highlightedCell;
							dispatchEvent(new Event(Event.CHANGE));
						}
						close();
						// Reset selectedIndex/prompt. Maybe dispatch change.
						break;
					case Keyboard.DOWN:
						open();
						break;
				}
				return;
			}

			event.stopPropagation();

			var pageSize:int=Math.max((calculateAvailableHeight() / list.rowHeight) << 0, 1);
			var sel:uint=selectedIndex;
			var lastSel:Number=(highlightedCell == -1) ? selectedIndex : highlightedCell;
			var newSel:int=-1;
			switch (event.keyCode)
			{
				case Keyboard.SPACE:
					isOpen ? close() : open();
					return;
				case Keyboard.ESCAPE:
					if (isOpen)
					{
						if (highlightedCell > -1)
						{
							selectedIndex=selectedIndex;
						}
						close();
					}
					return;

				case Keyboard.UP:
					newSel=Math.max(0, lastSel - 1);
					break;
				case Keyboard.DOWN:
					newSel=Math.min(length - 1, lastSel + 1);
					break;
				case Keyboard.PAGE_UP:
					newSel=Math.max(lastSel - pageSize, 0);
					break;
				case Keyboard.PAGE_DOWN:
					newSel=Math.min(lastSel + pageSize, length - 1);
					break;
				case Keyboard.HOME:
					newSel=0;
					break;
				case Keyboard.END:
					newSel=length - 1;
					break;

				case Keyboard.ENTER:
					if (_editable && highlightedCell == -1)
					{
						editableValue=inputField.text;
						selectedIndex=-1;
					}
					else if (isOpen && highlightedCell > -1)
					{
						editableValue=null;
						selectedIndex=highlightedCell;
						dispatchEvent(new Event(Event.CHANGE));
					}
					dispatchEvent(new ComponentEvent(ComponentEvent.ENTER));
					close();
					return;

				default:
					if (editable)
					{
						break;
					} // Don't allow letter keys to change focus when editable.
					newSel=list.getNextIndexAtLetter(String.fromCharCode(event.keyCode), lastSel);
					break;
			}

			if (newSel > -1)
			{
				if (isOpen)
				{
					highlightCell(newSel);
					inputField.text=list.itemToLabel(getItemAt(newSel));
				}
				else
				{
					highlightCell();
					selectedIndex=newSel;
					dispatchEvent(new Event(Event.CHANGE));
				}
			}
		}

		/**
		 * @private (protected)
		*/
		protected function highlightCell(index:int=-1):void
		{
			var renderer:ICellRenderer;

			// Turn off the currently highlighted cell
			if (highlightedCell > -1)
			{
				renderer=list.itemToCellRenderer(getItemAt(highlightedCell));
				if (renderer != null)
				{
					renderer.setMouseState("up");
				}
			}

			if (index == -1)
			{
				return;
			}

			// Scroll to the new index, so that the renderer is created
			list.scrollToIndex(index);
			list.drawNow();

			// Highlight the cellRenderer at the new index
			renderer=list.itemToCellRenderer(getItemAt(index));
			if (renderer != null)
			{
				renderer.setMouseState("over");
				highlightedCell=index;
			}
		}

		/**
		 * @private (protected)
		*/
		override protected function keyUpHandler(event:KeyboardEvent):void
		{
			isKeyDown=false;
		}

		/**
		 * @private (protected)
		 */
		override protected function initializeAccessibility():void
		{
			if (ComboBox.createAccessibilityImplementation != null)
			{
				ComboBox.createAccessibilityImplementation(this);
			}
		}
	}

}
